package com.fub.guidance;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fub.guidance.Vertex.Type;
import com.fub.util.PreferenceDAO;
import com.fub.util.Util;

public class Graph {

	private Map<String, Vertex> nodes;
	private List<Edge> edges;
	private List<Vertex> path;
	private List<Relation> relations;
	private static Graph instance;
	private DijkstraAlgorithm dijkstraAlgo;

	/**
	 * Returns an instance of this class (singleton pattern).
	 * 
	 * @return an instance of this class
	 */
	public static Graph getInstance() {
		if (instance == null) {
			instance = new Graph();
		}
		return instance;
	}

	/**
	 * Adds a Node to this graph.
	 * 
	 * @param nodes
	 *            the new node
	 */
	public void addNodes(List<Vertex> nodes) {
		this.nodes = createHashMapFromList(nodes);
	}

	/**
	 * Adds a relation to this graph.
	 * 
	 * @param nodes
	 *            the new relation
	 */
	public void addRealtions(List<Relation> relations) {
		this.relations = relations;
	}

	/**
	 * Adds a list of edges and calculates the edges weight.
	 * 
	 * @param edges
	 *            the edges
	 */
	public void setEdges(List<Edge> edges) {
		this.edges = edges;
	}

	/**
	 * Returns the calculated shortest path
	 * 
	 * @return
	 */
	public List<Vertex> getShortestPath() {
		return this.path;
	}

	/**
	 * Returns all nodes of the graph.
	 * 
	 * @return all nodes of the graph
	 */
	public Collection<Vertex> getNodes() {
		return this.nodes.values();
	}

	/**
	 * Returns all edges of the graph.
	 * 
	 * @return all edges of the graph
	 */
	public List<Edge> getEdges() {
		return this.edges;
	}

	/**
	 * Returns all relations of the graph.
	 * 
	 * @return all realations of the graph
	 */
	public List<Relation> getRelations() {
		return this.relations;
	}

	/**
	 * Returns all room labels.
	 * 
	 * @return all room labels
	 */
	public List<String> getAllRoomLabels() {
		ArrayList<String> roomNumbers = new ArrayList<String>();
		for (Vertex node : this.nodes.values()) {
			if ((node.getType() == Vertex.Type.ROOM)
					|| (node.getType() == Vertex.Type.ENTRANCE)) {
				if (!roomNumbers.contains(node.getLabel())) {
					roomNumbers.add(node.getLabel());
				}

				for (String key : node.getProperies().keySet()) {
					if (key.equals("imageurl"))
						continue;
					String value = node.getProperies().get(key);
					if (!roomNumbers.contains(value)) {
						roomNumbers.add(value);
					}
				}
			}
		}
		return roomNumbers;
	}

	/**
	 * Returns the neighbors of a node.
	 * 
	 * @param node
	 *            the node, whose neighbors are searched.
	 * @return all neighbors of the node
	 */
	public List<Vertex> getNeighbors(Vertex node) {
		List<Vertex> neighbors = new ArrayList<Vertex>();
		for (int i = 0; i < this.edges.size(); i++) {
			Edge edge = this.edges.get(i);
			Vertex source = getNodeById(edge.getSourceId());
			Vertex destination = getNodeById(edge.getDestinationId());
			if (source != null && destination != null) {
				if (source.equals(node)) {
					neighbors.add(destination);
				}
			}
		}
		return neighbors;
	}

	/**
	 * Returns a node on his id.
	 * 
	 * @param id
	 *            the id of the node
	 * @return the desired node
	 */
	public Vertex getNodeById(String id) {
		Vertex node = this.nodes.get(id);
		if (node == null) {

			throw new IllegalArgumentException(String.format(
					"No vertex found for id %s", id));
		}
		return node;
	}

	/**
	 * Returns true, if the shortest path between the source and destination has
	 * been calculated.
	 * 
	 * @param sourceID
	 *            the source of the shortest path
	 * @param destinationID
	 *            the destination of the shortest path
	 * @return true, if the shortest path between the source and destination has
	 *         been calculated
	 */
	public boolean isWayCalculated(String sourceID, String destinationID) {
		if (this.path == null) {
			return false;
		}
		boolean sourceCorect = this.path.get(0).getId() == sourceID;
		boolean destinationCorect = this.path.get(this.path.size() - 1).getId() == destinationID;
		return (sourceCorect && destinationCorect);
	}

	/**
	 * Calculates the shortestPath between source and destination.
	 * 
	 * @param sourceID
	 *            the source of the shortest path
	 * @param destinationID
	 *            the destination of the shortest path
	 */
	public void calculateWay(String sourceID, String destinationID) {
		if (this.dijkstraAlgo == null)
			this.dijkstraAlgo = new DijkstraAlgorithm(this.getEdges());
		this.dijkstraAlgo.execute(getNodeById(sourceID));
		this.path = this.dijkstraAlgo.getPath(getNodeById(destinationID));
	}

	private Map<String, Vertex> createHashMapFromList(List<Vertex> nodes) {
		HashMap<String, Vertex> result = new HashMap<String, Vertex>();
		for (Vertex node : nodes) {
			result.put(node.getId(), node);
		}
		return result;
	}

	public void calculateEdgeWeights(PreferenceDAO preferenceDAO) {
		for (Edge edge : this.edges) {
			Vertex source = getNodeById(edge.getSourceId());
			Vertex destination = getNodeById(edge.getDestinationId());
			int weight = calculateWeight(source, destination, preferenceDAO);
			edge.setWeight(weight);
		}
	}

	private int calculateWeight(Vertex source, Vertex destination,
			PreferenceDAO preferenceDAO) {
		float distance = Util.euclidianDistance(source.getPosition(),
				destination.getPosition());
		if (source.getType() == Type.STAIRS
				&& destination.getType() == Type.STAIRS) {
			if (preferenceDAO.isHandicapped()) {
				return 1000 * 100;
			}
			return Integer.parseInt(preferenceDAO.getStairsFactor()) * 100;
		}
		return (int) distance;
	}
}
